/*
* Copyright (C) 2004-2010  Autodesk, Inc.
* 
* This library is free software; you can redistribute it and/or
* modify it under the terms of version 2.1 of the GNU Lesser
* General Public License as published by the Free Software Foundation.
* 
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
* 
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*
*/

#include <stdafx.h>
#include "FdoCMSBoundingBox.h"
#include "FdoCMSXmlGlobals.h"

//#include "math.h"

#define MaxThreadsCount 3

#define LOGOHEIGHT 60
#define NOMAPLOGO   0
#define WITHONELOGO 0

static const double PI = (4.0 * ::atan2(1.0, 1.0));

const double EarthRadius = 6378137;
const double MinLatitude = -85.0511;
const double MaxLatitude = 85.0511;
const double MinLongitude = -180;
const double MaxLongitude = 180;


void FdoCMSBoundingBox::Verify()
{
    mMaxX = min(MaxLongitude, mMaxX);
    mMaxY = min(MaxLatitude, mMaxY);

    mMinX = max(MinLongitude, mMinX);
    mMinY = max(MinLatitude, mMinY);

    if(mMinX > mMaxX)
        mMinX = mMaxX;
    if(mMinY > mMaxY)
        mMinY = mMaxY;
}

FdoCMSBoundingBox::FdoCMSBoundingBox(void) :
mMinX(0),
mMinY(0),
mMaxX(0),
mMaxY(0)
{
}

FdoCMSBoundingBox::~FdoCMSBoundingBox(void)
{
}

void FdoCMSBoundingBox::Dispose()
{
    delete this;
}

FdoCMSBoundingBox* FdoCMSBoundingBox::Create()
{
    return new FdoCMSBoundingBox();
}

void FdoCMSBoundingBox::InitFromXml(FdoXmlSaxContext* context, FdoXmlAttributeCollection* attrs)
{
    try {
        VALIDATE_ARGUMENT(attrs);
        VALIDATE_ARGUMENT(context);

        FdoXmlAttributeP att = attrs->FindItem(FdoCMSXmlGlobals::CMSBoundingBoxMinX);
        if (att != NULL) {
            SetMinX(FdoCommonOSUtil::wtof(att->GetValue()));
        }

        att = attrs->FindItem(FdoCMSXmlGlobals::CMSBoundingBoxMinY);
        if (att != NULL) {
            SetMinY(FdoCommonOSUtil::wtof(att->GetValue()));
        }

        att = attrs->FindItem(FdoCMSXmlGlobals::CMSBoundingBoxMaxX);
        if (att != NULL) {
            SetMaxX(FdoCommonOSUtil::wtof(att->GetValue()));
        }

        att = attrs->FindItem(FdoCMSXmlGlobals::CMSBoundingBoxMaxY);
        if (att != NULL) {
            SetMaxY(FdoCommonOSUtil::wtof(att->GetValue()));
        }
    }
    catch (FdoException* ex) 
    {
        context->AddError(ex);
        ex->Release();
    }
}

FdoXmlSaxHandler* FdoCMSBoundingBox::XmlStartElement(FdoXmlSaxContext* context, FdoString* uri, FdoString* name, FdoString* qname, FdoXmlAttributeCollection* atts)
{
    return BaseType::XmlStartElement(context, uri, name, qname, atts);
}

FdoBoolean FdoCMSBoundingBox::XmlEndElement(FdoXmlSaxContext* context, FdoString* uri, FdoString* name, FdoString* qname)
{
    return BaseType::XmlEndElement(context, uri, name, qname);
}

FdoDouble FdoCMSBoundingBox::GetMinX() const
{
    return mMinX;
}

void FdoCMSBoundingBox::SetMinX(FdoDouble value)
{
    mMinX = value;
}

FdoDouble FdoCMSBoundingBox::GetMinY() const
{
    return mMinY;
}

void FdoCMSBoundingBox::SetMinY(FdoDouble value)
{
    mMinY = value;
}

FdoDouble FdoCMSBoundingBox::GetMaxX() const
{
    return mMaxX;
}

void FdoCMSBoundingBox::SetMaxX(FdoDouble value)
{
    mMaxX = value;
}

FdoDouble FdoCMSBoundingBox::GetMaxY() const
{
    return mMaxY;
}

void FdoCMSBoundingBox::SetMaxY(FdoDouble value)
{
    mMaxY = value;
}

/// <summary>
/// Clips a number to the specified minimum and maximum values.
/// </summary>
/// <param name="n">The number to clip.</param>
/// <param name="minValue">Minimum allowable value.</param>
/// <param name="maxValue">Maximum allowable value.</param>
/// <returns>The clipped value.</returns>
double Clip(double n, double minValue, double maxValue)
{
    return __min(__max(n, minValue), maxValue);
}
/// <summary>
/// Determines the map width and height (in pixels) at a specified level
/// of detail.
/// </summary>
/// <param name="levelOfDetail">Level of detail, from 1 (lowest detail)
/// to 23 (highest detail).</param>
/// <returns>The map width and height in pixels.</returns>
unsigned int MapSize(int levelOfDetail)
{
    return (unsigned int) 256 << levelOfDetail;
}
/// <summary>
/// Converts a point from latitude/longitude WGS-84 coordinates (in degrees)
/// into pixel XY coordinates at a specified level of detail.
/// </summary>
/// <param name="latitude">Latitude of the point, in degrees.</param>
/// <param name="longitude">Longitude of the point, in degrees.</param>
/// <param name="levelOfDetail">Level of detail, from 1 (lowest detail)
/// to 23 (highest detail).</param>
/// <param name="pixelX">Output parameter receiving the X coordinate in pixels.</param>
/// <param name="pixelY">Output parameter receiving the Y coordinate in pixels.</param>
void FdoCMSBoundingBox::LatLongToPixelXY(double latitude, double longitude, int levelOfDetail, int& pixelX, int& pixelY)
{
    latitude = Clip(latitude, MinLatitude, MaxLatitude);
    longitude = Clip(longitude, MinLongitude, MaxLongitude);

    double x = (longitude + 180) / 360; 
    double sinLatitude = sin(latitude * PI / 180);
    double y = 0.5 - log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * PI);

    unsigned int mapSize = MapSize(levelOfDetail);
    pixelX = (int) Clip(x * mapSize + 0.5, 0, mapSize - 1);
    pixelY = (int) Clip(y * mapSize + 0.5, 0, mapSize - 1);
}


/// <summary>
/// Converts a pixel from pixel XY coordinates at a specified level of detail
/// into latitude/longitude WGS-84 coordinates (in degrees).
/// </summary>
/// <param name="pixelX">X coordinate of the point, in pixels.</param>
/// <param name="pixelY">Y coordinates of the point, in pixels.</param>
/// <param name="levelOfDetail">Level of detail, from 1 (lowest detail)
/// to 23 (highest detail).</param>
/// <param name="latitude">Output parameter receiving the latitude in degrees.</param>
/// <param name="longitude">Output parameter receiving the longitude in degrees.</param>
void FdoCMSBoundingBox::PixelXYToLatLong(FdoSize pixelX, FdoSize pixelY, int levelOfDetail, double& latitude, double& longitude)
{
    double mapSize = MapSize(levelOfDetail);
    double x = (Clip((double)pixelX, 0, mapSize - 1) / mapSize) - 0.5;
    double y = 0.5 - (Clip((double)pixelY, 0, mapSize - 1) / mapSize);

    latitude = 90 - 360 * atan(exp(-y * 2 * PI)) / PI;
    longitude = 360 * x;
}


/// Returns tilex for the tile at longitude - lon and zoom level - zoom. 
/// The longitude must be in the range -180.0 <= lon < 180.0.  The zoom level must be a non-negative integer.
int FdoCMSBoundingBox::long2tilex(double lon, int z) 
{ 
    lon = Clip(lon, MinLongitude, MaxLongitude);   
    if(lon == MaxLongitude) --lon; 
    assert(lon >= MinLongitude && lon < MaxLongitude);
    return (int)(floor((lon + 180.0) / 360.0 * pow(2.0, z))); 
}

/// Returns tiley for the tile at latitude - lat and zoom level - zoom. 
/// The latitude must be in the range -85.0511 <= lat <= 85.0511.. The zoom level must be a non-negative integer.
int FdoCMSBoundingBox::lat2tiley(double lat, int z)
{ 
    lat = Clip(lat, MinLatitude, MaxLatitude);
    assert(lat >= MinLatitude && lat <= MaxLatitude);
    return (int)(floor((1.0 - log( tan(lat * PI/180.0) + 1.0 / cos(lat * PI/180.0)) / PI) / 2.0 * pow(2.0, z))); 
}

int FdoCMSBoundingBox::GetLogoHeight(size_t iX, size_t iY, size_t CountX, size_t CountY)
{
    int iLogoHeight = 0;
    if(NOMAPLOGO)
    {
        iLogoHeight = LOGOHEIGHT;
    }
    else
    {
        if(WITHONELOGO && (iY == 0 && iX == CountX - 1))
        {
            iLogoHeight = LOGOHEIGHT;
        }
    }
    return iLogoHeight;
}

void FdoCMSBoundingBox::GetCenter(double mMinX, double mMaxX, double mMinY, double mMaxY, double& latitude, double& longitude)
{
    int levelOfDetail = 16;
    int pixel_minX, pixel_minY, pixel_maxX, pixel_maxY;
    LatLongToPixelXY(mMinY, mMinX, levelOfDetail, pixel_minX, pixel_minY);
    LatLongToPixelXY(mMaxY, mMaxX, levelOfDetail, pixel_maxX, pixel_maxY);

    int pixel_X = (pixel_minX + pixel_maxX) / 2;
    int pixel_Y = (pixel_minY + pixel_maxY) / 2;
    PixelXYToLatLong(pixel_X, pixel_Y, levelOfDetail, latitude, longitude);
}

FdoDouble FdoCMSBoundingBox::GetCenterY() const
{
    double latitude, longitude;
    GetCenter(mMinX, mMaxX, mMinY, mMaxY, latitude, longitude);
    return latitude;
}

FdoDouble FdoCMSBoundingBox::GetCenterX() const
{
    return (mMinX+mMaxX)/2;
}


FdoCMSImageSpaceTile::FdoCMSImageSpaceTile(double mMinX, double mMaxX, double mMinY, double mMaxY, FdoSize mHeight, FdoSize mWidth
                                           , FdoSize mPixelStartX, FdoSize mPixelStartY)
{
    m_boundingBox = FdoCMSBoundingBox::Create();
    m_boundingBox->SetMaxX(mMaxX);
    m_boundingBox->SetMaxY(mMaxY);
    m_boundingBox->SetMinX(mMinX);
    m_boundingBox->SetMinY(mMinY);
    m_PixelStartX = mPixelStartX;
    m_PixelStartY = mPixelStartY;
    this->m_height = mHeight;
    m_width =  mWidth;
    m_pGdalDataset = NULL;
    m_pImageStream = NULL;
    memset(m_vfileNameTile, 0, 256 * sizeof(char));
}

FdoCMSImageSpaceTile::FdoCMSImageSpaceTile(FdoSize offsetX, FdoSize offsetY, FdoSize offsetHeight, FdoSize offsetWidth, FdoSize mHeight, FdoSize mWidth
                                           , FdoSize mPixelStartX, FdoSize mPixelStartY)
{
    m_boundingBox = FdoCMSBoundingBox::Create();
    m_boundingBox->SetMaxX(0);
    m_boundingBox->SetMaxY(0);
    m_boundingBox->SetMinX(0);
    m_boundingBox->SetMinY(0);
    m_PixelStartX = mPixelStartX;
    m_PixelStartY = mPixelStartY;
    this->m_height = mHeight;
    m_width =  mWidth;
    m_pGdalDataset = NULL;
    m_pImageStream = NULL;
    memset(m_vfileNameTile, 0, 256 * sizeof(char));
    m_offsetX = offsetX;
    m_offsetY = offsetY;
    m_offsetHeight = offsetHeight;
    m_offsetWidth = offsetWidth;
}



FdoCMSImageSpaceTile::~FdoCMSImageSpaceTile(void)
{
    //delete m_boundingBox;
    //m_boundingBox = NULL;
    if(strlen(m_vfileNameTile) > 0)
    {
        VSIUnlink(m_vfileNameTile);
        memset(m_vfileNameTile, 0, 256 * sizeof(char));
    }
    if(m_pGdalDataset)
    {
        GDALClose(m_pGdalDataset);
        m_pGdalDataset = NULL;
    }
}

GDALDataset* FdoCMSImageSpaceTile::GetCMSImageDataset(FdoIoStream* rasterStream)
{
    FdoByte* rasterBuffer;
    FdoInt32 rasterBufferSize;
    FdoInt32 rasterBufferCapacity;
    GDALDataset* gdalDataSet = NULL;
    if (gdalDataSet == NULL) {
        rasterBufferCapacity = 50 * 1024; // 50K might be a typical size of images from CMS servers
        rasterBufferSize = 0;
        rasterBuffer = new FdoByte[rasterBufferCapacity];
        do {
            FdoSize cntRead = rasterStream->Read (rasterBuffer + rasterBufferSize, rasterBufferCapacity - rasterBufferSize);
            if (cntRead == 0)
                break;
            rasterBufferSize += (FdoInt32)cntRead;
            // if the buffer is not large enough to hold the raster data, we need to enlarge the buffer
            if (rasterBufferSize == rasterBufferCapacity) {
                FdoByte* newBuffer = new FdoByte[rasterBufferCapacity * 2];
                memcpy(newBuffer, rasterBuffer, rasterBufferCapacity);
                delete[] rasterBuffer;
                rasterBuffer = newBuffer;
                rasterBufferCapacity *= 2;
            }

        } while (true);

        // we should reset the stream so later calls to "GetRaster" work fine.
        rasterStream->Reset ();
        // now we can release the raster stream since everything has been dumped into the buffer
        //rasterStream = NULL;
        // now create the virtual memory file from the buffer
        // NOTE: the file name for the virtual file must start with "/vsimem/" so that it could be recogized by 
        // GDAL as a virtual mem file
        //char vfileName[64];
        sprintf(m_vfileNameTile, "/vsimem/CMSImageDataset_%d", (FdoSize)rasterStream);
        FILE *fp = VSIFileFromMemBuffer(m_vfileNameTile, rasterBuffer, rasterBufferSize, FALSE );
        if( fp == NULL )
            throw FdoException::Create(FdoException::NLSGetMessage(FDO_57_UNEXPECTEDERROR, "Unexpected error encountered."));
        VSIFCloseL( fp );
        gdalDataSet = (GDALDataset*)GDALOpen(m_vfileNameTile, GA_ReadOnly);
        if (!gdalDataSet)
            throw FdoException::Create(FdoException::NLSGetMessage(FDOCMS_12006_NO_DATA_RETURNED, "The CMS Server returned no data from the GetMap request."));
    }
    return gdalDataSet;
}

int FdoCMSImageSpaceTile::SetIoStream(FdoIoStream* pIoStream)
{
    assert(NULL == m_pImageStream && pIoStream);
    m_pImageStream = pIoStream;
    m_pGdalDataset = GetCMSImageDataset(m_pImageStream);
    return m_pGdalDataset->GetRasterCount();
}

void FdoCMSImageSpaceTile::SetTileXY(FdoSize tileX, FdoSize tileY)
{
    m_tileX = tileX;
    m_tileY = tileY;
}

FdoSize FdoCMSImageSpaceTile::GetTileX()
{
    return m_tileX;
}

FdoSize FdoCMSImageSpaceTile::GetTileY()
{
    return m_tileY;
}

void FdoCMSImageSpaceTile::SetDelegateParams(DelegateParams* params)
{
    m_params = params;
}

DelegateParams* FdoCMSImageSpaceTile::GetDelegateParams()
{
    return m_params;
}

FdoCMSImageSpaceTileManager::FdoCMSImageSpaceTileManager(
    FdoSize heightMatched
    , FdoSize widthMatched) :m_heightAll(heightMatched), m_widthAll(widthMatched)
{
    m_currentLine = NULL;
    m_currentIndexX = -1;
    m_currentIndexY = 0;
    m_countX = 0;
    m_countY = 0;
    memset(m_vfileNameMerged, 0, 256 * sizeof(char));
    m_GDALDataset = NULL;
}

FdoCMSImageSpaceTileManager::~FdoCMSImageSpaceTileManager()
{
    Clear();
}

void FdoCMSImageSpaceTileManager::Clear()
{
    // TODO:
    // Lock
    FdoSize iSize = this->m_tilesSquare.size();
    for(FdoSize i = 0; i < iSize; i++)
    {
        FdoCMSImageSpaceLine* pLine = m_tilesSquare[i];
        FdoSize iLineNum = pLine->size();
        for(FdoSize j=0; j<iLineNum; j++)
        {
            FdoCMSImageSpaceTile* pTile = pLine->at(j);
            delete pTile;
        }
        pLine->clear();
        delete pLine;
    }

    m_tilesSquare.clear();

    if(m_GDALDataset)
    {
        GDALClose(m_GDALDataset);
        m_GDALDataset = NULL;
    }
    Reset();
    // Lock End
}

void FdoCMSImageSpaceTileManager::Reset()
{
    m_currentIndexX = -1;
    m_currentIndexY = 0;
    m_currentLine = NULL;
}

FdoSize FdoCMSImageSpaceTileManager::CountX() const
{
    return m_countX;
}
FdoSize FdoCMSImageSpaceTileManager::CountY() const
{
    return m_countY;
}


FdoCMSImageSpaceTile* FdoCMSImageSpaceTileManager::GetTile(FdoSize indexX, FdoSize indexY)
{
    if(indexX >= m_countX || indexY >= m_countY)
        return NULL;
    if(indexX >= m_tilesSquare.size())
        return NULL;
    FdoCMSImageSpaceLine* line = m_tilesSquare[indexX];
    if(NULL == line || indexY >= line->size())
        return NULL;
    return line->at(indexY);
}

bool FdoCMSImageSpaceTileManager::validate()
{
    // TODO:
    return true;
}


void FdoCMSImageSpaceTileManager::CurrentIndex(FdoSize& indexX, FdoSize& indexY)
{
    indexX = this->m_currentIndexX;
    indexY = this->m_currentIndexY;
}

void FdoCMSImageSpaceTileManager::BeginWrite()
{
    Clear();
}

void FdoCMSImageSpaceTileManager::EndWrite()
{
    validate();
    Reset();
}

void FdoCMSImageSpaceTileManager::BeginRead()
{
    this->m_mutex.Enter();
    Reset();
    this->m_mutex.Leave();
}

void FdoCMSImageSpaceTileManager::EndRead()
{
    this->m_mutex.Enter();
    Reset();
    this->m_mutex.Leave();
}

FdoCMSImageSpaceTile* FdoCMSImageSpaceTileManager::ReadNext()
{
    FdoCMSImageSpaceTile* pTileRet = NULL;
    this->m_mutex.Enter();
    do{

        if(m_currentIndexY >= this->m_countY)
            break;

        m_currentIndexX++;
        if(m_currentIndexX >= this->m_countX)
        {
            // Next column
            m_currentIndexX = 0;
            m_currentIndexY++;
            if(m_currentIndexY >= this->m_countY)
            {
                break;
            }
        }

        if(m_currentIndexX == 0)
        {
            if(m_currentIndexY >= m_tilesSquare.size())
            {
                break;
            }
            m_currentLine = m_tilesSquare[m_currentIndexY];
        }

        if(NULL != m_currentLine)
        {
            //assert()
            if(m_currentIndexX < m_currentLine->size())
            {
                pTileRet = m_currentLine->at(m_currentIndexX);
                break;
            }
        }

    }while(false);

    this->m_mutex.Leave();
    return pTileRet;
}


void FdoCMSImageSpaceTileManager::SetXYCount(FdoSize indexX, FdoSize indexY)
{
    Clear();
    Reset();
    m_countX = indexX;
    m_countY = indexY;
}

void FdoCMSImageSpaceTileManager::AppendTile(FdoCMSImageSpaceTile* spaceTile, FdoSize& indexX, FdoSize& indexY)
{
    m_currentIndexX++;
    if(m_currentIndexX >= this->m_countX)
    {
        // Next column
        m_currentIndexX = 0;
        m_currentIndexY++;
        if(m_currentIndexY >= this->m_countY)
        {
            Reset();
            return;
        }
    }

    if(m_currentIndexX == 0)
    {
        m_currentLine = new FdoCMSImageSpaceLine();
        m_tilesSquare.push_back(m_currentLine);
    }
    m_currentLine->push_back(spaceTile);
    indexX = m_currentIndexX;
    indexY = m_currentIndexY;
}

FdoByte RGBToIndex(const GDALColorTable* pColorTableNew, FdoByte red_value ,FdoByte green_value,FdoByte blue_value)
{
    int iCount = pColorTableNew->GetColorEntryCount();
    unsigned int iGapMin = 9000000;
    unsigned int iGapTemp = 0;
    FdoByte iIndex = 0;
    for(int i=0; i<iCount; i++)
    {
        const GDALColorEntry* pColorEntry = pColorTableNew->GetColorEntry(i);
        iGapTemp = (red_value - pColorEntry->c1) * (red_value - pColorEntry->c1)
            + (green_value - pColorEntry->c2) * (green_value - pColorEntry->c2) 
            + (blue_value - pColorEntry->c3) * (blue_value - pColorEntry->c3);
        if(iGapMin > iGapTemp)
        {
            iGapMin = iGapTemp;
            iIndex = i;
        }
    }
    return iIndex;
}

void GetColorIndexMap(const GDALColorTable* pColorTableFrom, const GDALColorTable* pColorTableTo, std::map<FdoByte, FdoByte>& indexMap)
{
    int iCount = pColorTableFrom->GetColorEntryCount();
    for(int i=0; i<iCount; i++)
    {
        const GDALColorEntry* pColorEntry = pColorTableFrom->GetColorEntry(i);
        indexMap[(FdoByte)i] = RGBToIndex(pColorTableTo, (FdoByte)pColorEntry->c1, (FdoByte)pColorEntry->c2, (FdoByte)pColorEntry->c3);
    }
}

static unsigned int iTemp = 100;

GDALDataset* FdoCMSImageSpaceTileManager::GetCMSImageDataset(FdoIoStream* rasterStream)
{
    FdoByte* rasterBuffer;
    FdoInt32 rasterBufferSize;
    FdoInt32 rasterBufferCapacity;
    GDALDataset* gdalDataSet = NULL;
    if (gdalDataSet == NULL) {
        rasterBufferCapacity = 50 * 1024; // 50K might be a typical size of images from CMS servers
        rasterBufferSize = 0;
        rasterBuffer = new FdoByte[rasterBufferCapacity];
        do {
            FdoSize cntRead = rasterStream->Read (rasterBuffer + rasterBufferSize, rasterBufferCapacity - rasterBufferSize);
            if (cntRead == 0)
                break;
            rasterBufferSize += (FdoInt32)cntRead;
            // if the buffer is not large enough to hold the raster data, we need to enlarge the buffer
            if (rasterBufferSize == rasterBufferCapacity) {
                FdoByte* newBuffer = new FdoByte[rasterBufferCapacity * 2];
                memcpy(newBuffer, rasterBuffer, rasterBufferCapacity);
                delete[] rasterBuffer;
                rasterBuffer = newBuffer;
                rasterBufferCapacity *= 2;
            }

        } while (true);

        // we should reset the stream so later calls to "GetRaster" work fine.
        rasterStream->Reset ();
        // now we can release the raster stream since everything has been dumped into the buffer
        //rasterStream = NULL;
        // now create the virtual memory file from the buffer
        // NOTE: the file name for the virtual file must start with "/vsimem/" so that it could be recogized by 
        // GDAL as a virtual mem file
        //char vfileName[64];
        sprintf(m_vfileNameMerged, "/vsimem/%d", (FdoSize)rasterStream);
        FILE *fp = VSIFileFromMemBuffer(m_vfileNameMerged, rasterBuffer, rasterBufferSize, FALSE );
        if( fp == NULL )
            throw FdoException::Create(FdoException::NLSGetMessage(FDO_57_UNEXPECTEDERROR, "Unexpected error encountered."));
        VSIFCloseL( fp );
        gdalDataSet = (GDALDataset*)GDALOpen(m_vfileNameMerged, GA_ReadOnly);
        if (!gdalDataSet)
            throw FdoException::Create(FdoException::NLSGetMessage(FDOCMS_12006_NO_DATA_RETURNED, "The CMS Server returned no data from the GetMap request."));
    }
    return gdalDataSet;
}



FdoIoStream*
FdoCMSImageSpaceTileManager::MergeImageTiles(int rasterBandNum)
{
    GDALDataset* m_GDALDataset = MergeImageTilesGDAL(rasterBandNum);
    GDALClose(m_GDALDataset);
    m_GDALDataset = NULL;
    vsi_l_offset outDataLength;
    int bUnlinkAndSeize = TRUE;
    GByte * binData = VSIGetMemFileBuffer(m_vfileNameMerged, &outDataLength, bUnlinkAndSeize);

    FdoIoMemoryStream* pIoStream = FdoIoMemoryStream::Create((FdoSize)outDataLength);
    pIoStream->Write(binData, (FdoSize)outDataLength);
    pIoStream->Reset();

    return pIoStream;

}

#include "FdoCommonThreadUtil.h"
#include "FdoCMSDelegateFactory.h"
#include "FdoCMSDelegate.h"
#include ".\YahooDelegate\FdoCMSYahooDelegate.h"
#include ".\YahooDelegate\FdoCMSYahooGetMap.h"

class ThreadContext
{
public:
    ThreadContext(FdoCMSImageSpaceTileManager* pTileManager)
    {
        m_pTileManager = pTileManager;
    }
    FdoString * url;
    int zoomLevel;
    FdoDouble ver;
    int* rasterBandNum;
    FdoCMSImageSpaceTileManager* m_pTileManager;
};

FDOCOMMON_THREAD_FUNCTION_SIGNATURE WorkerYahoo (void * arg)
{
    ThreadContext* pContext = (ThreadContext*)arg;
    
    //int id = pContext->threadId;
    try
    {
        FdoString * url = pContext->url;

        int* pRasterBandNum = pContext->rasterBandNum;
        FdoCMSImageSpaceTileManager* pTileMan = pContext->m_pTileManager;
        FdoCMSImageSpaceTile* pTile = pTileMan->ReadNext();
        while(pTile)
        {
            FdoCMSDelegateP pDelegate = FdoCMSDelegateFactory::Create(FdoCMSXmlGlobals::CMSYahooServiceName,L"");
            pDelegate->SetUrl(url);

            FdoPtr<FdoCMSYahooGetMap> request = FdoCMSYahooGetMap::Create(pTile->GetTileX(), pTile->GetTileY(), pContext->zoomLevel, pContext->ver);
            FdoPtr<FdoOwsResponse> response = pDelegate->Invoke (request);

            int rasterBandNumTemp = pTile->SetIoStream(response->GetStream());
            if(*pRasterBandNum == 0)
            {
                *pRasterBandNum = rasterBandNumTemp;
            }
            assert(*pRasterBandNum == rasterBandNumTemp);
            pTile = pTileMan->ReadNext();
        }
    }
    catch (FdoException* ex) 
    {
        assert(false);
        ex->Release();
    }
    delete pContext;
    return NULL;
}

#include "BingDelegate\FdoCMSBingGetMap.h"

FDOCOMMON_THREAD_FUNCTION_SIGNATURE WorkerBing(void * arg)
{
    ThreadContext* pContext = (ThreadContext*)arg;

    try
    {
        int* pRasterBandNum = pContext->rasterBandNum;
        FdoCMSImageSpaceTileManager* pTileMan = pContext->m_pTileManager;
        FdoCMSImageSpaceTile* pTile = pTileMan->ReadNext();
        while(pTile)
        {
            FdoCMSDelegateP pDelegate = FdoCMSDelegateFactory::CreateCommon(FdoCMSXmlGlobals::CMSBingServiceName);
            DelegateParams * params = pTile->GetDelegateParams();

            FdoPtr<FdoCMSBingGetMap> request = FdoCMSBingGetMap::Create(params->dCenterLat, 
                params->dCenterLon,
                params->iZoomLevel,
                params->height,
                params->width,
                params->imageFormat,
                params->mapType);
            FdoPtr<FdoOwsResponse> response = pDelegate->Invoke (request);

            int rasterBandNumTemp = pTile->SetIoStream(response->GetStream());
            if(*pRasterBandNum == 0)
            {
                *pRasterBandNum = rasterBandNumTemp;
            }
            assert(*pRasterBandNum == rasterBandNumTemp);

            pTile = pTileMan->ReadNext();
        }
    }
    catch (FdoException* ex) 
    {
        assert(false);
        ex->Release();
    }
    delete pContext;
    return NULL;
}

int FdoCMSImageSpaceTileManager::DoQueryBing(FdoSize tileCount)
{
    FDOCOMMON_THREAD_HANDLE * handles;

    FdoSize iThreadCount = __min(tileCount, MaxThreadsCount);
    handles = (FDOCOMMON_THREAD_HANDLE *)alloca( iThreadCount* sizeof(FDOCOMMON_THREAD_HANDLE));

    int rasterBandNum = 0;
    ThreadContext* pContext = NULL;
    int count = 0;
    this->BeginRead();
    // UNDONE: Only one thread can work. 
    for(int iX = 0; iX < iThreadCount; iX++)
    {
        pContext = new ThreadContext(this);
        pContext->rasterBandNum = &rasterBandNum;

        if (!FdoCommonThreadUtil::CreateThread(&(handles[iX]), (void*)WorkerBing, (void*)pContext))
        {
            //printf ("error (%d) starting thread #%d", errno, count);
            break;
        }
        else
        {
#ifdef _WIN32
            ::Sleep(500);
#endif // _WIN32
            count++;
        }
    }

    const int SECONDS = 2000*100;
    bool success = FdoCommonThreadUtil::WaitForThreads(count, handles, SECONDS * 1000);

    assert(rasterBandNum != 0);
    for (int i = 0; i < count; i++)
        FdoCommonThreadUtil::CloseThread(handles[i]);
    this->EndRead();

    //free(handles); // Why cannot be freed here?
    return rasterBandNum;
}

#include "GoogleDelegate\FdoCMSGoogleGetMap.h"

FDOCOMMON_THREAD_FUNCTION_SIGNATURE WorkerGoogle(void * arg)
{
    ThreadContext* pContext = (ThreadContext*)arg;

    try
    {
        int* pRasterBandNum = pContext->rasterBandNum;
        FdoCMSImageSpaceTileManager* pTileMan = pContext->m_pTileManager;
        FdoCMSImageSpaceTile* pTile = pTileMan->ReadNext();
        while(pTile)
        {
            FdoCMSDelegateP pDelegate = FdoCMSDelegateFactory::CreateCommon(FdoCMSXmlGlobals::CMSGoogleServiceName);
            DelegateParams * params = pTile->GetDelegateParams();

            FdoPtr<FdoCMSGoogleGetMap> request = FdoCMSGoogleGetMap::Create(params->dCenterLat, 
                params->dCenterLon,
                params->iZoomLevel,
                params->height,
                params->width,
                params->imageFormat,
                params->mapType,
                params->apiKey);
            FdoPtr<FdoOwsResponse> response = pDelegate->Invoke (request);

            int rasterBandNumTemp = pTile->SetIoStream(response->GetStream());
            if(*pRasterBandNum == 0)
            {
                *pRasterBandNum = rasterBandNumTemp;
            }
            assert(*pRasterBandNum == rasterBandNumTemp);

            pTile = pTileMan->ReadNext();
        }
    }
    catch (FdoException* ex) 
    {
        assert(false);
        ex->Release();
    }
    delete pContext;
    return NULL;
}

int FdoCMSImageSpaceTileManager::DoQueryGoogle(FdoSize tileCount)
{
    FDOCOMMON_THREAD_HANDLE * handles;

    FdoSize iThreadCount = __min(tileCount, MaxThreadsCount);
    handles = (FDOCOMMON_THREAD_HANDLE *)alloca( iThreadCount* sizeof(FDOCOMMON_THREAD_HANDLE));

    int rasterBandNum = 0;
    ThreadContext* pContext = NULL;
    int count = 0;
    this->BeginRead();
    // UNDONE: Only one thread can work. 
    for(int iX = 0; iX < iThreadCount; iX++)
    {
        pContext = new ThreadContext(this);
        pContext->rasterBandNum = &rasterBandNum;

        if (!FdoCommonThreadUtil::CreateThread(&(handles[iX]), (void*)WorkerGoogle, (void*)pContext))
        {
            //printf ("error (%d) starting thread #%d", errno, count);
            break;
        }
        else
        {
#ifdef _WIN32
            ::Sleep(500);
#endif // _WIN32
            count++;
        }
    }

    const int SECONDS = 2000*100;
    bool success = FdoCommonThreadUtil::WaitForThreads(count, handles, SECONDS * 1000);

    assert(rasterBandNum != 0);
    for (int i = 0; i < count; i++)
        FdoCommonThreadUtil::CloseThread(handles[i]);
    this->EndRead();

    //free(handles); // Why cannot be freed here?
    return rasterBandNum;
}


int FdoCMSImageSpaceTileManager::DoQueryYahoo(FdoSize tileCount, FdoString* url, int zoomLevel, FdoDouble ver)
{
    FDOCOMMON_THREAD_HANDLE * handles;

    FdoSize iThreadCount = __min(tileCount, MaxThreadsCount);

    handles = (FDOCOMMON_THREAD_HANDLE *)alloca( iThreadCount* sizeof(FDOCOMMON_THREAD_HANDLE));

    ThreadContext* pContext = NULL;
    int count = 0;
    // UNDONE: Only one thread can work. 
    int rasterBandNum = 0;
    for(int iX = 0; iX < iThreadCount; iX++)
    {

        pContext = new ThreadContext(this);
        pContext->url = url;
        pContext->zoomLevel = zoomLevel;
        pContext->ver = ver;
        pContext->rasterBandNum = &rasterBandNum;

        if (!FdoCommonThreadUtil::CreateThread(&(handles[iX]), (void*)WorkerYahoo, (void*)pContext))
        {
            //printf ("error (%d) starting thread #%d", errno, count);
            break;
        }
        else
        {
#ifdef _WIN32
            ::Sleep(500);
#endif // _WIN32
            count++;
        }
    }

    const int SECONDS = 2000*100;
    bool success = FdoCommonThreadUtil::WaitForThreads(count, handles, SECONDS * 1000);

    for (int i = 0; i < count; i++)
        FdoCommonThreadUtil::CloseThread(handles[i]);

    //free(handles); // Why cannot be freed here?
    assert(rasterBandNum != 0);
    return rasterBandNum;
}

GDALDataset*
FdoCMSImageSpaceTileManager::MergeImageTilesGDALYahoo(int rasterBandNum, FdoSize offsetX, FdoSize offsetY, FdoSize heightMatched, FdoSize widthMatched)
{
    bool bOneImageOnly = (1 == CountY()) && (1 == CountX());
    if(bOneImageOnly)
    {
        FdoCMSImageSpaceTile* pTile = GetTile(0, 0);
        assert(pTile->m_pGdalDataset);
        return pTile->m_pGdalDataset;
    }
    else
    {
        GDALDriver * targetDriver = NULL;
        targetDriver = GetGDALDriverManager()->GetDriverByName("GTiff");
        if( targetDriver == NULL )
        {
            assert(0);
            return NULL;
        }

        iTemp++;
        sprintf(m_vfileNameMerged, "/vsimem/MergeImageTilesGDAL_%d.tif", iTemp);

        GDALDataset * dataset_out = targetDriver->Create(m_vfileNameMerged, (int)widthMatched, (int)heightMatched, rasterBandNum, GDT_Byte, NULL);
        if(dataset_out == NULL )
        {
            assert(0);
            return NULL;
        }

        int iOutBandNum = dataset_out->GetRasterCount();
        assert(iOutBandNum == rasterBandNum);
        for(int iBand = 1; iBand <= rasterBandNum; iBand++)
        {
            GDALRasterBand* pBandOut1 = dataset_out->GetRasterBand(iBand);
            assert(pBandOut1);
            if(NULL == pBandOut1)
                break;
            CPLErr err0;
            BeginRead();
            GDALColorTable* pColorTableNew = NULL;
            for(FdoSize iY = 0; iY < CountY(); iY++)
            {
                for(FdoSize iX = 0; iX < CountX(); iX++)
                {
                    FdoCMSImageSpaceTile* pTile = ReadNext();
                    if(NULL != pTile)
                    {
                        if(NULL != pTile->m_pGdalDataset)
                        {
                            GDALRasterBand* pBand1 = pTile->m_pGdalDataset->GetRasterBand(iBand);

                            if(iX == 0 && iY == 0)
                            {
                                err0 = pBandOut1->SetColorInterpretation(pBand1->GetColorInterpretation());
                                assert(CE_None == err0);
                            }
                            FdoSize nXSize1 = pTile->m_offsetWidth;
                            FdoSize nYSize1 = pTile->m_offsetHeight;
                            FdoByte* pBuffer1 = (FdoByte *) CPLMalloc(sizeof(FdoByte)*nXSize1*nYSize1); 

                            GDALDataType dataType1 = pBand1->GetRasterDataType();
                            assert(GDT_Byte == dataType1);

                            err0 = pBand1->RasterIO(GF_Read, (int)pTile->m_offsetX, (int)pTile->m_offsetY, (int)nXSize1, (int)nYSize1, pBuffer1, (int)nXSize1, (int)nYSize1, dataType1, 0, 0 );

                            // TODO:
                            // Weired for yahoo, it sometimes returned CE_Failure
                            // So comment here temporarily and expect future investigation.
                            // assert(CE_None == err0); 
                            
                            if(CE_None == err0)
                            {
                                if(rasterBandNum == 1)
                                {
                                    if(iX == 0 && iY == 0)
                                    {
                                        const GDALColorTable* pColorTable1 = pBand1->GetColorTable();
                                        pColorTableNew = pColorTable1->Clone();
                                        err0 = pBandOut1->SetColorTable(pColorTableNew);
                                        assert(CE_None == err0);
                                    }
                                    else
                                    {
                                        GDALColorTable* pColorTable1 = pBand1->GetColorTable();
                                        std::map<FdoByte, FdoByte> indexMap;
                                        GDALColorTable* pColorTableFrom = pColorTable1->Clone();
                                        GetColorIndexMap(pColorTableFrom, pColorTableNew, indexMap);
                                        for(int iBufIndex = 0; iBufIndex < nXSize1*nYSize1; iBufIndex++)
                                        {
                                            pBuffer1[iBufIndex] = indexMap[pBuffer1[iBufIndex]];
                                        }
                                    }
                                }
                                //assert(pTile->m_width == nXSize1);
                                //assert(pTile->m_height == nYSize1);
                                //assert(m_heightAll >= pTile->m_PixelStartY + pTile->m_height);
                                //assert(m_widthAll >= pTile->m_PixelStartX + pTile->m_width);
                                err0 = pBandOut1->RasterIO(GF_Write, (int)pTile->m_PixelStartX, (int)pTile->m_PixelStartY
                                    , (int)nXSize1, (int)nYSize1, pBuffer1, (int)nXSize1, (int)nYSize1, dataType1, 0 , 0);
                                assert(CE_None == err0);
                            }
                            if(CE_None != err0)
                                err0 = CE_None;
                            CPLFree(pBuffer1);
                        }
                        else
                        {
                            assert(0);
                        }
                    }
                    else
                    {
                        assert(0);
                    }
                }
            }
            EndRead();
        }


        GDALClose(dataset_out);
        dataset_out = (GDALDataset*)GDALOpen(m_vfileNameMerged, GA_ReadOnly);
        m_GDALDataset = dataset_out;
        return dataset_out;
    }
}

GDALDataset*
FdoCMSImageSpaceTileManager::MergeImageTilesGDAL(int rasterBandNum)
{
    int iLogoHeightOneTile = FdoCMSBoundingBox::GetLogoHeight(0, 0, 1, 1);
    bool bOneImageOnly = (1 == CountY()) && (1 == CountX()) && (iLogoHeightOneTile == 0);

    if(bOneImageOnly)
    {
        FdoCMSImageSpaceTile* pTile = GetTile(0, 0);
        assert(pTile->m_pGdalDataset);
        return pTile->m_pGdalDataset;
    }
    else
    {
        GDALDriver * targetDriver = NULL;
        targetDriver = GetGDALDriverManager()->GetDriverByName("GTiff");
        if( targetDriver == NULL )
        {
            assert(0);
            return NULL;
        }

        // static unsigned int iTemp = 0;
        iTemp++;
        sprintf(m_vfileNameMerged, "/vsimem/MergeImageTiles_%d.tif", iTemp);


        GDALDataset * dataset_out = targetDriver->Create(m_vfileNameMerged, (int)m_widthAll, (int)m_heightAll, rasterBandNum, GDT_Byte, NULL);
        if(dataset_out == NULL )
        {
            assert(0);
            return NULL;
        }

        int iOutBandNum = dataset_out->GetRasterCount();
        assert(iOutBandNum == rasterBandNum);
        for(int iBand = 1; iBand <= rasterBandNum; iBand++)
        {
            GDALRasterBand* pBandOut1 = dataset_out->GetRasterBand(iBand);
            assert(pBandOut1);
            if(NULL == pBandOut1)
                break;
            CPLErr err0;
            BeginRead();
            GDALColorTable* pColorTableNew = NULL;
            for(FdoSize iY = 0; iY < CountY(); iY++)
            {
                for(FdoSize iX = 0; iX < CountX(); iX++)
                {
                    FdoCMSImageSpaceTile* pTile = ReadNext();
                    if(NULL != pTile)
                    {
                        if(NULL != pTile->m_pGdalDataset)
                        {
                            GDALRasterBand* pBand1 = pTile->m_pGdalDataset->GetRasterBand(iBand);

                            if(iX == 0 && iY == 0)
                            {
                                err0 = pBandOut1->SetColorInterpretation(pBand1->GetColorInterpretation());
                                assert(CE_None == err0);
                            }
                            FdoSize nXSize1 = pTile->m_width;
                            FdoSize nYSize1 = pTile->m_height;
                            FdoByte* pBuffer1 = (FdoByte *) CPLMalloc(sizeof(FdoByte)*nXSize1*nYSize1); 

                            GDALDataType dataType1 = pBand1->GetRasterDataType();
                            assert(GDT_Byte == dataType1);

                            int iLogoHeight = FdoCMSBoundingBox::GetLogoHeight(iX, iY, CountX(), CountY());
                            err0 = pBand1->RasterIO(GF_Read, 0, iLogoHeight, (int)nXSize1, (int)nYSize1, pBuffer1, (int)nXSize1, (int)nYSize1, dataType1, 0, 0 );
                            //assert(CE_None == err0);

                            if(rasterBandNum == 1)
                            {
                                if(iX == 0 && iY == 0)
                                {
                                    const GDALColorTable* pColorTable1 = pBand1->GetColorTable();
                                    pColorTableNew = pColorTable1->Clone();
                                    err0 = pBandOut1->SetColorTable(pColorTableNew);
                                    assert(CE_None == err0);
                                }
                                else
                                {
                                    GDALColorTable* pColorTable1 = pBand1->GetColorTable();
                                    std::map<FdoByte, FdoByte> indexMap;
                                    GDALColorTable* pColorTableFrom = pColorTable1->Clone();
                                    GetColorIndexMap(pColorTableFrom, pColorTableNew, indexMap);
                                    for(int iBufIndex = 0; iBufIndex < nXSize1*nYSize1; iBufIndex++)
                                    {
                                        pBuffer1[iBufIndex] = indexMap[pBuffer1[iBufIndex]];
                                    }
                                }
                            }

                            assert(pTile->m_width == nXSize1);
                            assert(pTile->m_height == nYSize1);
                            assert(m_heightAll >= pTile->m_PixelStartY + pTile->m_height);
                            assert(m_widthAll >= pTile->m_PixelStartX + pTile->m_width);
                            err0 = pBandOut1->RasterIO(GF_Write, (int)pTile->m_PixelStartX, (int)pTile->m_PixelStartY
                                , (int)pTile->m_width, (int)pTile->m_height, pBuffer1, (int)pTile->m_width, (int)pTile->m_height, dataType1, 0 , 0);
                            assert(CE_None == err0);
                            if(CE_None != err0)
                                err0 = CE_None;
                            CPLFree(pBuffer1);
                        }
                        else
                        {
                            assert(0);
                        }
                    }
                    else
                    {
                        assert(0);
                    }
                }
            }
            EndRead();
        }

        GDALClose(dataset_out);
        dataset_out = (GDALDataset*)GDALOpen(m_vfileNameMerged, GA_ReadOnly);
        m_GDALDataset = dataset_out;
        return dataset_out;
    }
}
